﻿using System.Collections.Generic;
using System.Linq;
using SymbolIntegration.core.Expressions;
using SymbolIntegration.core.Expressions.Functions;

namespace SymbolIntegration.core.Helpers
{
    static public class ParseUtils
    {
        public static Expression ParseExpression(string expression)
        {
            int errorPosition;
            if (!CheckScopes(expression, out errorPosition))
            {
                throw new ParseException("Open scopes don't match closed scopes", expression, 0, expression.Length, errorPosition);
            }
            var parsedExpr = Parse(expression, 0, expression.Length);
            return parsedExpr;
        }

        public static bool CheckScopes(string expression, out int errorPosition, char left = '(', char right = ')')
        {
            int openScopesCount = 0;
            errorPosition = expression.Length;
            for (int i = 0; i < expression.Length; i++)
            {
                char t = expression[i];
                if (t == left)
                {
                    openScopesCount++;
                }
                else if (t == right)
                {
                    openScopesCount--;
                    if (openScopesCount < 0)
                    {
                        errorPosition = i;
                        break;
                    }
                }
            }

            bool result = openScopesCount == 0;
            return result;
        }

        public static int FindCloseScopePosition(string expression, int beginScopePosition, int size, char left = '(', char right = ')')
        {
            int result = -1;
            int openScopesCount = 0;
            for (int i = beginScopePosition; i < beginScopePosition + size; i++)
            {
                if (expression[i] == left)
                {
                    openScopesCount++;
                }
                else if (expression[i] == right)
                {
                    openScopesCount--;
                }

                if (openScopesCount == 0)
                {
                    result = i;
                    break;
                }
            }

            return result;
        }


        public static void TrimScopes(string expression, ref int position, ref int size)
        {
            int startPosition = position;
            int endPosition = startPosition + size - 1;

            bool isFoundSpace;

            do
            {
                isFoundSpace = false;
                while (startPosition <= endPosition && expression[startPosition] == ' ')
                {
                    startPosition++;
                    isFoundSpace = true;
                }

                while (startPosition <= endPosition && expression[endPosition] == ' ')
                {
                    endPosition--;
                    isFoundSpace = true;
                }

                while (startPosition <= endPosition && (expression[startPosition] == '('
                    && FindCloseScopePosition(expression, startPosition, endPosition - startPosition + 1) == endPosition))
                {
                    startPosition++;
                    endPosition--;
                    isFoundSpace = true;
                }   
            }
            while (isFoundSpace);

            position = startPosition;
            size = endPosition - startPosition + 1;
            if (size < 0)
                size = 0;
        }

        internal static Expression Parse(string expression, int position, int size)
        {
            TrimScopes(expression, ref position, ref size);
            string subExpression = expression.Substring(position, size);
            Expression result;

            do
            {
                if (subExpression == "")
                {
                    throw new ParseException("Expression is scopes or spaces", expression, position, size, 0);
                }

                var outerSymbols = GetOuterOperationSymbols(expression, position, size);
                if (subExpression[0] == '-' && outerSymbols.Count == 0)
                {
                    result = Parse(expression, position + 1, size - 1);
                    result = result.InvertNegate(true);
                    break;
                }
                
                if (subExpression.All(c => char.IsNumber(c) || c == ','))
                {
                    result = ConstantExpression.ParseItself(expression, position, size);
                    break;
                }

                if (subExpression.All(char.IsLetterOrDigit))
                {
                    result = VariableExpression.ParseItself(expression, position, size);
                    break;
                }

                if (outerSymbols == null || outerSymbols.Count == 0)
                {
                    result = TryParseFunction(expression, position, size);
                    break;
                }

                if (outerSymbols.Exists(s => s == '+' || s == '-'))
                {
                    result = AddendsExpression.ParseItself(expression, position, size);
                    break;
                }

                if (outerSymbols.Exists(s => s == '*'))
                {
                    result = MultipliersExpression.ParseItself(expression, position, size);
                    break;
                }

                if (outerSymbols.Exists(s => s == '/'))
                {
                    result = DivideExpression.ParseItself(expression, position, size);
                    break;
                }

                if (outerSymbols.Exists(s => s == '^'))
                {
                    result = PowerExpression.ParseItself(expression, position, size);
                    break;
                }

                string sOuterOperations = "";
                outerSymbols.Foreach(c => sOuterOperations += c);
                throw new ParseException("Unknown outer operations: " + sOuterOperations, expression, position, size, 0);
            } 
            while (false);

            return result;
        }

        private static List<char> GetOuterOperationSymbols(string expression, int position, int size)
        {
            var result = new List<char>();
            int end = position + size - 1;
            if (expression[position] == '-')
            {
                position++;
            }
            for (int i = position; i <= end; i++)
            {
                if (expression[i] == '(')
                {
                    i = FindCloseScopePosition(expression, i, end - i + 1);
                    continue;
                }
                if (!IsSpaceOrLetterOrDigit(expression[i]))
                {
                    result.Add(expression[i]);
                }
            }
            return result;
        }

        static private FuncExpression TryParseFunction(string expression, int position, int size)
        {
            int start = position;
            int end = position + size - 1;
            while (start <= end && IsSpaceOrLetterOrDigit(expression[start]))
            {
                start++;
            }
            if (start > end)
            {
                throw new ParseException("Expression is not function, but expected it.", expression,
                    position, size, 0);
            }
            if (expression[start] != '(' && FindCloseScopePosition(expression, start, size) != end)
            {
                throw new ParseException("Invalid argument for function. Expected argument in scopes", expression,
                    position, size, start);
            }

            string functionName = expression.Substring(position, start - position);
            functionName = functionName.Trim();
            FuncExpression funcExpression;
            switch (functionName)
            {
                case "exp":
                    funcExpression = ExpFunction.ParseItself(expression, start, end - start + 1);
                    break;
                case "ln":
                    funcExpression = LnFunction.ParseItself(expression, start, end - start + 1);
                    break;
                case "sin":
                    funcExpression = SinFunction.ParseItself(expression, start, end - start + 1);
                    break;
                case "cos":
                    funcExpression = CosFunction.ParseItself(expression, start, end - start + 1);
                    break;
                case "tg":
                    funcExpression = TgFunction.ParseItself(expression, start, end - start + 1);
                    break;
                default:
                    throw new ParseException("Unknown function name " + functionName, expression, position, size,
                        position);
            }

            return funcExpression;
        }

        private static bool IsSpaceOrLetterOrDigit(char c)
        {
            return char.IsWhiteSpace(c) || char.IsLetterOrDigit(c) || c == ',';
        }
    }
}
